package com.example.demo.retry;

import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONUtil;
//import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.*;

/**
 * <p>
 * 方法重试记录表 服务实现类
 * </p>
 *
 * @author 赵儒定
 * @since 2022-3-8
 */
//@Service
public class SysMethodRetryLogServiceImpl {//extends ServiceImpl<SysMethodRetryLogMapper, SysMethodRetryLog> implements ISysMethodRetryLogService {

//    @Override
    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public boolean retryLog(ProceedingJoinPoint point, Object result, Throwable ex) {
        String env = KongAdminContext.KongEnv.get();
        MethodSignature signature = (MethodSignature) point.getSignature();
        Method method = signature.getMethod();
        List<Param> paramList = getParamMap(method,point.getArgs());
        String clazz = method.getDeclaringClass().getName();
        String methodName = method.getName();
        String param = JSONUtil.toJsonStr(paramList);
        String threadLocalMapStr = getThreadLocalMap();
        String md5Key = DigestUtil.md5Hex(clazz+methodName+param+threadLocalMapStr,"UTF-8");
        String res = getResult(result);
        boolean isError = ex!=null;
        SysMethodRetryLog retryLog1 = null;//this.getById(md5Key);
        if(retryLog1==null){
            if(!isError){
                return false;
            }
            RetryLog log = method.getAnnotation(RetryLog.class);
            SysMethodRetryLog retryLog = new SysMethodRetryLog();
            retryLog.setId(md5Key);
            retryLog.setClazz(clazz);
            retryLog.setMethod(methodName);
            retryLog.setParams(JSONUtil.toJsonStr(paramList));
            retryLog.setThreadLocalMap(threadLocalMapStr);
            retryLog.setIsError(true);
            retryLog.setResult(isError?ex.toString():res);
            retryLog.setMethodDesc((StringUtils.isBlank(env)?"":"["+env+"]")+log.value());
            //this.save(retryLog);
            return true;
        }
        SysMethodRetryLog retryLog2 = new SysMethodRetryLog();
        retryLog2.setId(md5Key);
        retryLog2.setIsError(isError);
        retryLog2.setResult(isError?ex.toString():res);
        retryLog2.setRetryNum(retryLog1.getRetryNum()+1);
        //this.updateById(retryLog2);
        return false;
    }

    private String getThreadLocalMap() {
        Map<String, Object> map = new HashMap<>();
        if(KongAdminContext.KongPrimaryTid.get()!=null){
            map.put(KongAdminContext.class.getName()+"@KongPrimaryTid",KongAdminContext.KongPrimaryTid.get());
        }
        if(KongAdminContext.KongEnv.get()!=null){
            map.put(KongAdminContext.class.getName()+"@KongEnv",KongAdminContext.KongEnv.get());
        }
        if(KongAdminContext.KongTid.get()!=null){
            map.put(KongAdminContext.class.getName()+"@KongTid",KongAdminContext.KongTid.get());
        }
        return JSONUtil.toJsonStr(map);
    }

    //@Override
    public SysMethodRetryLog retry(String id) {
        SysMethodRetryLog retryLog = null;//this.getById(id);
        retryLog.setResult(RetryUtil.retryMethod(retryLog.getClazz(),retryLog.getMethod(),retryLog.getParams(),retryLog.getThreadLocalMap()));
        return retryLog;
    }

    private String getResult(Object result) {
        if(result==null){
            return null;
        }
        return JSONUtil.toJsonStr(result);
    }

    private String getMd5Key(String clazz, String methodName, String param) {
        return DigestUtil.md5Hex(clazz+methodName+param,"UTF-8");
    }

    private static final List<Class> PRIMITIVE_TYPES = Arrays.asList(String.class,
            Integer.class,
            Long.class,
            Float.class,
            Double.class,
            BigDecimal.class);

    private List<Param> getParamMap(Method method, Object[] args) {
        if(args==null||args.length==0){
            return null;
        }
        List<Param> list = new ArrayList<>();
        Class<?>[] parameterTypes = method.getParameterTypes();
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            Object arg = args[i];
            if(parameterType.isPrimitive()||PRIMITIVE_TYPES.contains(parameterType)){
                list.add(new Param(parameterType.getName(),arg==null?null:arg.toString()));
            }else if(Collection.class.isAssignableFrom(parameterType)){
                list.add(new Param(genericParameterTypes[i].getTypeName(),arg==null?null:JSONUtil.toJsonStr(arg)));
            }else if(parameterType.isEnum()){
                Enum e = (Enum) arg;
                list.add(new Param(parameterType.getName(),e==null?null:e.name(),true));
            }else{
                list.add(new Param(parameterType.getName(),arg==null?null:JSONUtil.toJsonStr(arg)));
            }
        }
        return list;
    }

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    public static class Param{
        private String type;
//        private String genericType;
        private String value;

        private Boolean isEnum;

        public Param(String type, String value) {
            this.type = type;
            this.value = value;
        }

        public Param(String type, String value,Boolean isEnum) {
            this.type = type;
            this.value = value;
            this.isEnum = isEnum;
        }

        private Class<?> clazz;

        private Class<?> genericClazz;


    }
}
